#ifndef ADAPTER_ADAPTER_MANAGER_H_
#define ADAPTER_ADAPTER_MANAGER_H_
#include <iostream>

template <typename C,typename D>
class Adapter{
public:
  typedef D DataType;
  typedef std::shared_ptr<D const> DataPtr; 
  typedef C ClassType
  typedef std::shared_ptr<C const> ClassPtr; 

  typedef typename std::list<DataPtr>::const_iterator Iterator;
  typedef typename std::function<void(const D&)> Callback;

  Adapter(std::shared_ptr<ClassType>& instance, const std::string& topic_name,
             size_t message_num)
      : node_handle_(instance), topic_name_(topic_name), message_num_(message_num) {}                                                             
  ~Adapter() {}

  const std::string& topic_name() const {
    return topic_name_;
  }

  template <typename InputMessageType>
  void SubPublish(const InputMessageType& data,
                  const std::string& channel) {
    node_handle_->publish(channel, &data);
  }

  void Publish(const D& data)
  {    
    if (!topic_name_.empty()) {
      SubPublish<D>(data, topic_name_);
    }
  }

  void ClearData() {
    // Lock the queue.
    std::lock_guard<std::mutex> lock(mutex_);
    data_queue_.clear();
  } 

   void AddCallback(const Callback& callback, const std::string& name = "default") {
    if (name != "default") {
      auto it = callbacks_map_.find(name);
      if (it != callbacks_map_.end()) {
        return;
      }
      callbacks_map_.emplace(name, callback);
    } else {
      callbacks_map_.emplace(std::to_string(callbacks_map_.size()), callback);
    }                                                                                                                                                                 
  }

  template <class T> void AddCallback(void (T::*fp)(const D& data), T* obj) {
    AddCallback(std::bind(fp, obj, std::placeholders::_1));
  }
  void RemoveCallback(const std::string& name) override {
    auto it = callbacks_map_.find(name);
    if (it != callbacks_map_.end()) {
      callbacks_map_.erase(it);
    }
  }

  void AdapterCallback(const D& data) {
    for (const auto& callback : callbacks_map_) {
      callback.second(data);
    }
  }

  void EnqueueData(DataPtr data) {
    // Don't try to copy data and enqueue if the message_num is 0
    if (message_num_ == 0) {
      return;
    }
    // Lock the queue.
    std::lock_guard<std::mutex> lock(mutex_);
    if (data_queue_.size() + 1 > message_num_) {
      data_queue_.pop_back();
    }
    data_queue_.push_front(data);
  }

  private:
    std::shared_ptr<ClassType> node_handle_;
    std::list<DataPtr> data_queue_;
    mutable std::mutex mutex_;
    size_t message_num_ = 10;
    std::unordered_map<std::string, Callback> callbacks_map_;
};

#endif